#include "base/disk_file.h"

char* DiskFile::Mmap(int prot, int flag) {
  if (data_ != NULL) return data_;

  data_ = (char*) ::mmap(NULL, size(), prot, flag, fd_, 0);
  if (data_ == MAP_FAILED) {
    PLOG(WARNING)<< "mmap error";
    data_ = NULL;
  }

  return data_;
}

int32 DiskFile::Read(char* buf, int32 size) const {
  if (buf == NULL || size < 0) return -1;
  int32 left = size;
  while (left > 0) {
    int32 readn = ::read(fd_, buf, left);
    if (readn == -1) {
      if (errno == EINTR) {
        continue;
      }
      PLOG(WARNING)<< "read error, fd: " << fd_;
      break;
    } else if (readn == 0) {
      return 0;
    }

    buf += readn;
    left -= readn;
  }

  return size - left;
}

int32 DiskFile::Write(const char* buf, int32 size) const {
  if (buf == NULL || size < 0) return -1;
  int32 left = size;
  while (left > 0) {
    int32 writen = ::write(fd_, buf, left);
    if (writen == -1) {
      if (errno == EINTR) {
        continue;
      }
      PLOG(WARNING)<< "write error, fd: " << fd_;
      return -1;
    }

    left -= writen;
    buf += writen;
  }

  return size - left;
}

int32 DiskFile::Read(char* buf, int32 size, int32 offset) const {
  if (buf == NULL || size < 0 || offset < 0) return -1;
  int32 left = size;

  while (left > 0) {
    int32 readn = ::pread(fd_, buf, left, offset);
    if (readn == -1) {
      if (errno == EINTR) {
        continue;
      }
      LOG(WARNING)<< "pread error, fd: " << fd_;
      break;
    }

    buf += readn;
    left -= readn;
    offset += readn;
  }

  return size - left;
}

int32 DiskFile::Write(const char* buf, int32 size, int32 offset) const {
  if (buf == NULL || size < 0 || offset < 0) return -1;
  int32 left = size;

  while (left > 0) {
    int32 writen = ::pwrite(fd_, buf, left, offset);
    if (writen == -1) {
      if (errno == EINTR) {
        continue;
      }
      LOG(WARNING)<< "pwrite error, fd: " << fd_;
      break;
    }

    buf += writen;
    left -= writen;
    offset += writen;
  }
  return size - left;
}

int32 DiskFile::Read(struct iovec *iov, int32 iovcnt) const {
  if (iov == NULL || iovcnt < 0) return -1;
  int32 readn;
  while (true) {
    readn = ::readv(fd_, iov, iovcnt);
    if (readn == -1) {
      if (errno == EINTR) {
        continue;
      }
      PLOG(WARNING)<< "readv error, fd: " << fd_;
      return -1;
    }
    break;
  }

  return readn;
}

void DiskFile::Write(const struct iovec* iov, int32 iovcnt) const {
  if (iov == NULL || iovcnt < 0) return;
  int32 writen;
  while (true) {
    writen = ::writev(fd_, iov, iovcnt);
    if (writen == -1) {
      if (errno == EINTR) {
        continue;
      }
      PCHECK(0) << "writev error, fd: " << fd_;
    }
    break;
  }

  int sum = 0;
  for (int i = 0; i < iovcnt; ++i) {
    sum += iov[i].iov_len;
  }
  CHECK_EQ(sum, writen)<< "writen: " << writen << " sum: " << sum;
}

void DiskFile::CopyFrom(const DiskFile& rhs) const {
#define IOV_MAX 8
#define BUF_SIZE 4096
#define ONCE_TOTAL (IOV_MAX * BUF_SIZE)

  struct iovec iov[IOV_MAX];
  char buf[IOV_MAX][BUF_SIZE];

  for (int i = 0; i < IOV_MAX; ++i) {
    iov[i].iov_base = buf[i];
    iov[i].iov_len = BUF_SIZE;
  }

  while (true) {
    int32 readn = rhs.Read(iov, IOV_MAX);
    if (readn == 0) {
      break;
    } else if (readn < ONCE_TOTAL) {
      for (int i = 0; i < IOV_MAX; ++i) {
        if (readn > BUF_SIZE) {
          iov[i].iov_len = BUF_SIZE;
          readn -= BUF_SIZE;
        } else if (readn < BUF_SIZE) {
          iov[i].iov_len = readn;
          readn = 0;
        } else {
          iov[i].iov_len = 0;
        }
      }
    }

    Write(iov, IOV_MAX);
  }
}
